;;; init-org.el --- Org-mode settings -*- lexical-binding: t -*-
;;; Commentary:

;;; Code:

;; org-mode 基础配置
(use-package org
  :ensure nil
  :mode ("\\.org\\'" . org-mode)
  :bind (:map org-mode-map
              ("C-S-e" . #'org-emphasize)                           ; 快速添加 org 定义的强调符号
              ("<f5>"  . #'font-lock-update)                        ; 重新渲染 font-lock
              ("<f8>"  . #'org-latex-preview)                       ; 预览 latex
              ("C-\""  . #'+org/org-preview-image-link-in-posframe) ; 使用 posfrmae 预览 org 内的图片链接
              ("M-o"   . #'+transient/org))
  :init
  (setq org-modules-loaded t)           ; 标记所有模块已经加载，避免加载一些用不到的模块
  :custom-face
  (org-block-begin-line ((t (:background unspecified :height 0.9))))
  (org-block-end-line ((t (:background unspecified :height 0.9))))
  :custom
  (org-startup-truncated nil)            ; 折行
  (org-startup-indented t)               ; 缩进
  (org-startup-numerated t)              ; 标题编号
  (org-startup-with-inline-images nil)   ; 默认不显示内联图片
  (org-fontify-whole-heading-line t)     ; 标题行应用特殊字体样式
  (org-support-shift-select t)
  (org-fontify-quote-and-verse-blocks t) ; #+BEGIN_QUOTE #+BEGIN_VERSE 使用特殊字体样式
  (org-startup-folded 'showall)          ; 控制 org 文件打开时标题的折叠状态
  (org-mouse-1-follows-link nil)         ; 鼠标不再点击打开链接
  (org-image-actual-width nil)           ; 不使用图片自身尺寸
  (org-ellipsis " «")
  (org-confirm-babel-evaluate nil)            ; 禁用每次 org-babel-execute 后的确认
  (org-M-RET-may-split-line '((default . t))) ; 当光标位于文本中间时，按下 M-RET 把文本截断
  (org-insert-heading-respect-content t)
  (org-log-into-drawer t)               ; 日志条目（如状态变更记录）放入指定的抽屉中
  (org-log-done 'time)                  ; 任务的完成事件记录日志
  (org-todo-keywords
   '((sequence "TODO(t)" "WAIT(w!)" "|" "CANCEL(c!)" "DONE(d!)")))
  :config
  ;; 在echo area中显示链接详情
  (require 'help-at-pt)
  (setq help-at-pt-display-when-idle t
        help-at-pt-timer-delay 0.5)
  (help-at-pt-set-timer)

  ;; 渲染 latex，如果是 windows 系统则需安装 MiKTeX
  (setq org-preview-latex-default-process 'dvisvgm
        org-preview-latex-process-alist
        '((dvisvgm
           :programs ("xelatex" "dvisvgm")
           :description "xdv > svg"
           :message "you need to install the programs: xelatex and dvisvgm."
           :image-input-type "xdv"
           :image-output-type "svg"
           :image-size-adjust (0.9 . 0.9)
           :latex-compiler ("xelatex --no-pdf -interaction nonstopmode -output-directory %o %f")
           :image-converter ("dvisvgm %f -n -b min -c %S -o %O"))))

  ;; 扩展 Org emphasis 非 ASCII 字符
  (setq org-emphasis-regexp-components
        (list
         (concat " \t('\"{[:nonascii:]")
         (concat "- \t.,:!?;'\")}\\[[:nonascii:]")
         " \t\r\n,\"'"
         "."
         1))
  ;; 重新生成正则
  (org-set-emph-re 'org-emphasis-regexp-components org-emphasis-regexp-components)

  ;; From https://github.com/Elilif/.elemacs/blob/ff4f2e3076de5aa653479f37b77d294940d0a828/lib/lib-org.el#L58
  (defun eli/org-element--parse-generic-emphasis (mark type)
    "Parse emphasis object at point, if any.

MARK is the delimiter string used.  TYPE is a symbol among
`bold', `code', `italic', `strike-through', `underline', and
`verbatim'.

Assume point is at first MARK."
    (save-excursion
      (let ((origin (point)))
        (unless (bolp) (forward-char -1))
        (let ((opening-re
               (rx-to-string
                `(seq (or line-start (any space ?- ?\( ?' ?\" ?\{ nonascii))
                      ,mark
                      (not space)))))
          (when (looking-at opening-re)
            (goto-char (1+ origin))
            (let ((closing-re
                   (rx-to-string
                    `(seq
                      (not space)
                      (group ,mark)
                      (or (any space ?- ?. ?, ?\; ?: ?! ?? ?' ?\" ?\) ?\} ?\\ ?\[
                               nonascii)
                          line-end)))))
              (when (re-search-forward closing-re nil t)
                (let ((closing (match-end 1)))
                  (goto-char closing)
                  (let* ((post-blank (skip-chars-forward " \t"))
                         (contents-begin (1+ origin))
                         (contents-end (1- closing)))
                    (list type
                          (append
                           (list :begin origin
                                 :end (point)
                                 :post-blank post-blank)
                           (if (memq type '(code verbatim))
                               (list :value
                                     (and (memq type '(code verbatim))
                                          (buffer-substring
                                           contents-begin contents-end)))
                             (list :contents-begin contents-begin
                                   :contents-end contents-end)))))))))))))
  (advice-add #'org-element--parse-generic-emphasis :override #'eli/org-element--parse-generic-emphasis)

  ;; 按需加载 org-babel language
  (defun +org/org-babel-execute-src-block (&optional _arg info _params)
    "Load language if needed"
    (let* ((lang (nth 0 info))
           (sym (if (member (downcase lang) '("c" "cpp" "c++")) 'C (intern lang)))
           (backup-languages org-babel-load-languages))
      ;; - (LANG . nil) 明确禁止的语言，不加载。
      ;; - (LANG . t) 已加载过的语言，不重复载。
      (unless (assoc sym backup-languages)
        (condition-case err
            (progn
              (org-babel-do-load-languages 'org-babel-load-languages (list (cons sym t)))
              (setq-default org-babel-load-languages (append (list (cons sym t)) backup-languages)))
          (file-missing
           (setq-default org-babel-load-languages backup-languages)
           err)))))
  (advice-add 'org-babel-execute-src-block :before #'+org/org-babel-execute-src-block)

  ;; 导出文件到下载文件夹
  (defun +org/org-export-output-file-name-modified (orig-fun extension &optional subtreep pub-dir)
    (let ((pub-dir "~/Downloads")
          (assets-dir (expand-file-name ".assets" (file-name-directory (buffer-file-name)))))
      (when (file-directory-p assets-dir)
        ;; 解析当前buffer，找出所有图片链接
        (let ((used-images (org-element-map (org-element-parse-buffer) 'link
                             (lambda (link)
                               (when (string= (org-element-property :type link) "file")
                                 (let ((path (org-element-property :path link)))
                                   (when (and (image-type-from-file-name path)
                                              (string-prefix-p (file-name-as-directory assets-dir)
                                                               (expand-file-name path)))
                                     (expand-file-name path))))))))
          ;; 创建目标.assets目录
          (let ((target-dir (expand-file-name ".assets" pub-dir)))
            (make-directory target-dir t)
            ;; 复制使用的图片
            (dolist (image used-images)
              (copy-file image (expand-file-name (file-name-nondirectory image) target-dir) t)))))
      (apply orig-fun extension subtreep pub-dir nil)))
  (advice-add 'org-export-output-file-name :around #'+org/org-export-output-file-name-modified))

;; org 导出为 markdown
(use-package ox-md :after ox :ensure nil)

;; 异步 org-babel-execute
(use-package ob-async :defer t :ensure t)

(use-package ob-html :defer t :ensure (:host github :repo "misohena/ob-html"))

;; Dependences: npm install -g @mermaid-js/mermaid-cli
(use-package ob-mermaid :defer t :ensure t :config (setq org-babel-mermaid-cli-path "mmdc"))

;; Dependences: plantuml/plantuml.jar
(use-package ob-plantuml
  :defer t
  :ensure nil
  :init
  (when (eq system-type 'windows-nt)
    (setq org-plantuml-exec-mode 'jar
          org-plantuml-jar-path (expand-file-name "plantuml.jar" no-littering-etc-directory)
          org-plantuml-args '("-Dfile.encoding=UTF-8" "-Djava.awt.headless=true" "--illegal-access=deny"))))

;; Dependences: cargo install rust-script
(use-package ob-rust :defer t :ensure t)

(use-package org-capture
  :ensure nil
  :hook (org-capture-mode . (lambda ()
                              (setq-local org-complete-tags-always-offer-all-agenda-tags t)
                              (delete-other-windows)))
  :custom
  (org-capture-use-agenda-date t)       ; 光标所处的 agenda 日期作为新条目的时间
  :config
  (setq org-capture-templates
        `(("t" "Tasks" entry (file+headline ,(expand-file-name "tasks.org" org-directory) "Reminders")
           "* TODO %i%?"
           :empty-lines-after 1
           :prepend t)
          ("b" "Bookmarks" entry (file+headline ,(expand-file-name "capture.org" org-directory) "Bookmarks")
           "* %:description\n\n%a%?"
           :empty-lines 1
           :immediate-finish t)
          ("d" "Diary")
          ("dt" "Today's TODO list" entry (file+olp+datetree ,(expand-file-name "diary.org" org-directory))
           "* Today's TODO list [/]\n%T\n\n** TODO %?"
           :empty-lines 1
           :jump-to-captured t)
          ("do" "Other stuff" entry (file+olp+datetree ,(expand-file-name "diary.org" org-directory))
           "* %?\n%T\n\n%i"
           :empty-lines 1
           :jump-to-captured t))))

(use-package org-agenda
  :ensure nil
  :config
  (setq org-agenda-files `(,(expand-file-name "tasks.org" org-directory)))
  (setq org-agenda-current-time-string (concat "← Now " (make-string 70 ?·)))
  (setq org-agenda-time-grid
        '((daily today require-timed)
          ( 0500 0600 0700 0800 0900 1000
            1100 1200 1300 1400 1500 1600
            1700 1800 1900 2000 2100 2200)
          "" ""))
  
  (defun +agenda--org-agenda-format-date-aligned (date)
    (require 'cal-iso)
    (let* ((dayname (calendar-day-name date t))
           (day (cadr date))
           (day-of-week (calendar-day-of-week date))
           (month (car date))
           (monthname (calendar-month-name month t))
           (year (nth 2 date))
           (iso-week (org-days-to-iso-week
                      (calendar-absolute-from-gregorian date)))
           ;; (weekyear (cond ((and (= month 1) (>= iso-week 52))
           ;;                  (1- year))
           ;;                 ((and (= month 12) (<= iso-week 1))
           ;;                  (1+ year))
           ;;                 (t year)))
           (weekstring (if (= day-of-week 1)
                           (format " (W%02d)" iso-week)
                         "")))
      (format "%s %2d %s %4d%s"
              dayname day monthname year weekstring)))
  (setq org-agenda-format-date #'+agenda--org-agenda-format-date-aligned))

(use-package denote
  :ensure t
  :hook (dired-mode . denote-dired-mode-in-directories)
  :init
  (with-eval-after-load 'org-capture
    (setq denote-org-capture-specifiers "%l\n%i\n%?")
    (add-to-list 'org-capture-templates
                 '("n" "New note (with denote.el)" plain
                   (file denote-last-path)
                   #'denote-org-capture
                   :no-save t
                   :immediate-finish nil
                   :kill-buffer t
                   :jump-to-captured t)))
  :config
  (setq denote-directory (concat org-directory "denote/"))
  (setq denote-infer-keywords t)
  (setq denote-sort-keywords t)
  (setq denote-file-type nil) ; org is default, set others such as text, markdown-yaml, markdown-toml
  (setq denote-prompts '(title keywords)) ; file name format

  ;; We allow multi-word keywords by default.  The author's personal
  ;; preference is for single-word keywords for a more rigid workflow.
  (setq denote-allow-multi-word-keywords t)
  (setq denote-date-format nil))

;; 启用变宽字体
(use-package mixed-pitch
  :ensure t
  :hook ((org-mode . variable-pitch-mode)
         (org-mode . mixed-pitch-mode))
  :custom
  (mixed-pitch-variable-pitch-cursor cursor-type)
  :config
  (setq mixed-pitch-fixed-pitch-faces
        (delq 'org-table mixed-pitch-fixed-pitch-faces)))

;; .org 渲染美化
(use-package org-modern
  :when (display-graphic-p)
  :ensure t
  :hook (org-mode . org-modern-mode)
  :config
  (setq org-modern-star 'replace ; options 'fold,'replace or nil
        org-modern-list '((?+ . "◦")
                          (?- . "•")
                          (?* . "◦"))
        org-modern-block-name nil
        org-modern-block-fringe nil ; 禁止在 fringe 绘制代码框线
        org-modern-table nil))

;; emphasis-markers 自动显隐
(use-package org-appear
  :ensure t
  :hook (org-mode . org-appear-mode)
  :custom
  (org-hide-emphasis-markers t)
  (org-appear-autoemphasis t)
  (org-appear-autosubmarkers t)
  (org-appear-autoentities t)
  (org-appear-autolinks t)
  (org-appear-autokeywords t)
  (org-appear-inside-latex t))

(use-package ftable
  :ensure t
  :commands (ftable-fill ftable-reformat ftable-edit-cell)
  :config
  (eval-after-load 'ftable
    (setopt ftable-fill-column 80)))

;; .org 文件目录生成
(use-package toc-org
  :ensure t
  :hook (org-mode . toc-org-mode))

;; 快速向 .org 中插入图片，图片的来源可以是路径/网址
(use-package org-download
  :defer 3
  :ensure t
  :custom
  (org-download-display-inline-images 'posframe) ; 从posframe中预览图片
  (org-download-image-attr-list
   '("#+ATTR_ORG: :width 600px\n#+ATTR_HTML: :width 600px")) ; 定义插入图片后对图片的描述信息
  :config
  (setq org-download-annotate-function
        (lambda (link)
          (let ((name (file-name-nondirectory link))) ; 提取文件名
            (format "#+DOWNLOADED: %s @ %s\n"
                    (if (equal link org-download-screenshot-file)
                        "screenshot"
                      name)
                    (format-time-string "%Y-%m-%d %H:%M:%S")))))

  ;; From: gitee.com/mickey991/emacs-config/blob/master/emacs-config.org
  (defun +org--org-download-method (link)
    "Instead of method default method, the image will be saved to the specified path"
    (let ((filename (file-name-nondirectory (car (url-path-and-query (url-generic-parse-url link)))))
          (imgdir (concat (file-name-directory (buffer-file-name)) ".assets/")))
      (setq org-download-image-dir imgdir)
      (make-directory imgdir t)
      (expand-file-name (funcall org-download-file-format-function filename) imgdir)))
  (setq org-download-method '+org--org-download-method))

(use-package pangu-spacing
  :ensure t
  :hook (org-mode . (lambda ()
                      (add-hook 'before-save-hook
                                #'pangu-spacing-space-current-buffer
                                nil t)))
  :config
  (setq pangu-spacing-real-insert-separator t))

(provide 'init-org)
;;; init-org.el ends here
